package jmine.tec.script.impl;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import jmine.tec.script.ScriptParameters;
import jmine.tec.utils.Tuple;
import jmine.tec.utils.UnmodifiableTuple;

/**
 * Implementa {@link ScriptParameters}
 * 
 * @author takeshi
 */
public class ScriptParametersImpl implements ScriptParameters {

    private final Map<String, Tuple<Object, Class<?>>> parameterMap = new HashMap<String, Tuple<Object, Class<?>>>();

    private final Map<String, Object> constants = new HashMap<String, Object>();

    /**
     * {@inheritDoc}
     * 
     * @see jmine.tec.script.ScriptParameters#defineParameter(java.lang.String, java.lang.Class)
     */
    public void defineParameter(String name, Class<?> type) {
        Tuple<Object, Class<?>> tuple = parameterMap.get(name);
        if (tuple == null) {
            tuple = new UnmodifiableTuple<Object, Class<?>>(null, type);
        } else {
            tuple = new UnmodifiableTuple<Object, Class<?>>(tuple.head(), type);
        }
        parameterMap.put(name, tuple);
    }

    /**
     * {@inheritDoc}
     * 
     * @see jmine.tec.script.ScriptParameters#getConstants()
     */
    public Map<String, Object> getConstants() {
        return new HashMap<String, Object>(constants);
    }

    /**
     * {@inheritDoc}
     * 
     * @see jmine.tec.script.ScriptParameters#getParameterDefinitions()
     */
    public Map<String, Class<?>> getParameterDefinitions() {
        Map<String, Class<?>> def = new HashMap<String, Class<?>>(this.parameterMap.size());
        Set<Entry<String, Tuple<Object, Class<?>>>> entrySet = parameterMap.entrySet();
        for (Entry<String, Tuple<Object, Class<?>>> entry : entrySet) {
            def.put(entry.getKey(), entry.getValue().tail());
        }
        return def;
    }

    /**
     * {@inheritDoc}
     * 
     * @see jmine.tec.script.ScriptParameters#getParameterValues()
     */
    public Map<String, Object> getParameterValues() {
        Map<String, Object> params = new HashMap<String, Object>(parameterMap.size());
        Set<Entry<String, Tuple<Object, Class<?>>>> entrySet = parameterMap.entrySet();
        for (Entry<String, Tuple<Object, Class<?>>> entry : entrySet) {
            params.put(entry.getKey(), entry.getValue().head());
        }
        return params;
    }

    /**
     * {@inheritDoc}
     * 
     * @see jmine.tec.script.ScriptParameters#putParameter(java.lang.String, java.lang.Object)
     */
    public void putParameter(String name, Object parameter) {
        putParameter(name, parameter, Object.class);
    }

    /**
     * {@inheritDoc}
     * 
     * @see jmine.tec.script.ScriptParameters#putParameter(java.lang.String, java.lang.Object, java.lang.Class)
     */
    public void putParameter(String name, Object parameter, Class<?> type) {
        this.parameterMap.put(name, new UnmodifiableTuple<Object, Class<?>>(parameter, type));
    }

    /**
     * {@inheritDoc}
     * 
     * @see jmine.tec.script.ScriptParameters#putParameter(java.lang.String, java.lang.Object, java.lang.Class)
     */
    public void putConstant(String name, char value) {
        this.constants.put(name, value);
    }

    /**
     * {@inheritDoc}
     * 
     * @see jmine.tec.script.ScriptParameters#putParameter(java.lang.String, java.lang.Object, java.lang.Class)
     */
    public void putConstant(String name, byte value) {
        this.constants.put(name, value);
    }

    /**
     * {@inheritDoc}
     * 
     * @see jmine.tec.script.ScriptParameters#putParameter(java.lang.String, java.lang.Object, java.lang.Class)
     */
    public void putConstant(String name, short value) {
        this.constants.put(name, value);
    }

    /**
     * {@inheritDoc}
     * 
     * @see jmine.tec.script.ScriptParameters#putParameter(java.lang.String, java.lang.Object, java.lang.Class)
     */
    public void putConstant(String name, int value) {
        this.constants.put(name, value);
    }

    /**
     * {@inheritDoc}
     * 
     * @see jmine.tec.script.ScriptParameters#putParameter(java.lang.String, java.lang.Object, java.lang.Class)
     */
    public void putConstant(String name, long value) {
        this.constants.put(name, value);
    }

    /**
     * {@inheritDoc}
     * 
     * @see jmine.tec.script.ScriptParameters#putParameter(java.lang.String, java.lang.Object, java.lang.Class)
     */
    public void putConstant(String name, float value) {
        this.constants.put(name, value);
    }

    /**
     * {@inheritDoc}
     */
    public void putConstant(String name, double value) {
        this.constants.put(name, value);
    }

    /**
     * {@inheritDoc}
     */
    public void putConstant(String name, boolean value) {
        this.constants.put(name, value);
    }

    /**
     * {@inheritDoc}
     */
    public void putConstant(String name, String value) {
        this.constants.put(name, value);
    }

    /**
     * {@inheritDoc}
     */
    public <E extends Enum<?>> void putConstant(String name, E value) {
        this.constants.put(name, value);
    }

    /**
     * @param values Map
     * @param types Map
     * @param constants Map
     * @return {@link ScriptParameters}
     */
    static ScriptParameters fromMaps(Map<String, Object> values, Map<String, Class<?>> types, Map<String, Object> constants) {
        ScriptParametersImpl impl = new ScriptParametersImpl();
        impl.constants.putAll(constants);
        for (Entry<String, Object> entry : values.entrySet()) {
            impl.putParameter(entry.getKey(), entry.getValue());
        }
        for (Entry<String, Class<?>> entry : types.entrySet()) {
            impl.defineParameter(entry.getKey(), entry.getValue());
        }
        return impl;
    }

    /**
     * {@inheritDoc}
     * 
     * @see jmine.tec.script.ScriptParameters#getParameterDefinition(java.lang.String)
     */
    public Tuple<Object, Class<?>> getParameterDefinition(String name) {
        return this.parameterMap.get(name);
    }

}
